连接

  在所有的编译单位中,对所有函数、类、模板、变量、名字空间、枚举和枚举符的名字的使用都必须保持一致,除非它们被显式地描述为局部的东西。

  所有名字空间、类、函数等都应该在它们出现的各个编译单位中有适当的声明,而且所有声明都应该一致地引用同一个实体,保证这一点是程序员的工作。例如,考虑两个文件

    // file1.c
        int x = 1;
        int f() { /* 做某些事情 */ }

    // file2.c
        extern int x;
        int f();
        void g() { x = f(); }

在file2.c里使用的x和f()就是在file1.c里定义的那些东西。关键字extern指明在file2.c里x的声明(只)是一个声明,而不是一个定义(4.9节)。如果在这里出现x的初始式,那么就简单地忽略extern,因为带有初始式的声明就是定义。在一个程序里,一个对象只能定义一次,它可以有多个声明,但类型必须完全一样。例如,

    // file1.c
        int x = 1;
        int b = 1;
        extern int c;

    // file2.c
        int x;                // 意思是int x = 0;
        extern double b;
        extern int c;

这里存在着3个错误❌:x定义了两次,b用不同的类型定义了两次,而c只声明了两次但没有定义。一个每次只看一个文件的编译器将无法检查出这些种类的错误(连接错误),这些错误中的大部分可以被连接器检查出来。请注意,如果定义在全局作用域或者名字空间作用域里某一个变量没有初始式,它就会被按照默认方式初始化。对局部变量(4.9.5节、10.4.2节)和在自由存储中建立的对象(6.2.6节)而言,情况就不是这样。例如,

    // file1.c:
        int x;
        int f() { return x; }

    // file2.c:
        int x;
        int g() { return f(); }

程序片段中包含两个错误。

在file2.c里调用f()是个错误,因为f()没有在file2.c里声明。同样,程序也无法连接,因为x定义了两次。注意,在C语言中这个f()调用不是错误(B.2.2节)。

  如果一个名字可以在与其定义所在的编译单位不同的地方使用,就说它具有外部连接的。前面例子里的所有名字都具有外部连接。如果某个名字只能在其定义所在的编译单位内部使用,它就被称为是具有内部连接的。

  一个inline函数(7.1.1节、10.2.9节)必须在需要用它的每个编译单位里定义---通过完全一样的定义(9.2.3节)。因此,下面的例子不仅是一个极坏的尝试,它是非法的:

    // file1.c:
        inline int f(int i) { return i; }

    // file2.c:
        inline int f(int i) { return i + 1; }

不幸的是,这种错误却很难被具体实现检查出来。也正是因为这种情况,另一种本来具有完美逻辑的东西---外部连接和在线的组合---只好也禁止了,以便使写编辑器的人们的工作更简单一些:

    // file1.c:
        extern inline int g(int i);
        int h(int i) { return g(i); }        // 错误❌:g()在这个编译单位里无定义

    // file2.c:
        extern inline int g(int i) { return i + 1; }

按照默认约定,const(5.4节)和typedef(4.9.7节)都具有内部连接。因此下面的例子就是合法的(虽然很可能把人弄糊涂):

    // file1.c
        typedef int T;
        const int x = 7;

    // file2.c:
        typedef void T;
        const int x = 8;

局部于一个编译单位的全局变量是造成混乱的一个常见根源,最好是避免之。为了保证一致性,你一般应该把全局的const和inline都仅仅放在头文件里(9.2.1节)。

  通过显式声明可以使const具有外部连接:

    // file1.c:
        extern const int a = 77;

    // file2.c
        extern const int a;

        void g()
        {
            cout << a << '\n';
        }

在这里g()将打印出77。

  无名名字空间(8.2.5节)可以用于使一些名字局部于一个编译单位。无名名字空间的效果很像是内部连接。例如,

    // file1.c
        namespace {
            class X { /* ... */ };
            void f();
            int i;
            // ...
        }

    // file2.c
        class X { /* ... */ };
        void f();
        int i;
        // ...

位于file1.c里的函数f()与file2.c里的f()不是同一个函数。让一个名字局部于某个编译单位,而又将同一个名字用在其他地方,表示一个具有外部连接的实体,这样做实在是自找麻烦。

  在C和C++程序里,关键字static也被(有些混乱地)用于表示“使用内部连接”(B.2.3节)。请不要使用static,除了在函数(7.1.2节)和类(10.2.4节)的内部。

🔚